/*
 * @(#)XfslErrorFunction.java        1.0 2000/05/09
 *
 * This file is part of Xfuzzy 3.0, a design environment for fuzzy logic
 * based systems.
 *
 * (c) 2000 IMSE-CNM. The authors may be contacted by the email address:
 *                    xfuzzy-team@imse.cnm.es
 *
 * Xfuzzy is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * Xfuzzy is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

package xfuzzy.xfsl.model;

import xfuzzy.lang.*;
import xfuzzy.xfds.XfdsDataSet;

/**
 * Clase que describe una funci�n de error
 * 
 * @author Francisco Jos� Moreno Velo
 *
 */
public class XfslErrorFunction implements Cloneable {
	
	//----------------------------------------------------------------------------//
	//                            COSTANTES P�BLICAS                              //
	//----------------------------------------------------------------------------//

	/**
	 * Error cuadr�tico medio
	 */
	public static final int MEAN_SQUARE_ERROR = 0;
	
	/**
	 * Error cuadr�tico ponderado
	 */
	public static final int W_MEAN_SQUARE_ERROR = 1;
	
	/**
	 * Error absoluto medio
	 */
	public static final int MEAN_ABS_ERROR = 2;
	
	/**
	 * Error absoluto ponderado
	 */
	public static final int W_MEAN_ABS_ERROR = 3;
	
	/**
	 * Error de clasificaci�n
	 */
	public static final int CLASIF_ERROR = 4;
	
	/**
	 * Error de clasificaci�n avanzado
	 */
	public static final int ADV_CLASIF_ERROR = 5;
	
	/**
	 * Error cuadr�tico de clasificaci�n
	 */
	public static final int NEFCLASS = 6;

	//----------------------------------------------------------------------------//
	//                            MIEMBROS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * C�digo de la funci�n de error
	 */
	private int code;
	
	/**
	 * Pesos de las funciones ponderadas
	 */
	private double[] weight;

	//----------------------------------------------------------------------------//
	//                                CONSTRUCTOR                                 //
	//----------------------------------------------------------------------------//

	/**
	 * Constructor utilizado en la interfaz gr�fica
	 */
	public XfslErrorFunction(int code) throws XflException {
		if(code < 0) throw new XflException(25);
		this.code = code;
		this.weight = new double[0];
	}

	/**
	 * Constructor usado en la configuraci�n
	 */
	public XfslErrorFunction(String name) throws XflException {
		this(nameToCode(name));
	}

	/**
	 * Constructor con pesos usado en la configuraci�n
	 */
	public XfslErrorFunction(String name, double[] weights) throws XflException {
		this(nameToCode(name));
		setWeights(weights);
	}

	//----------------------------------------------------------------------------//
	//                             M�TODOS P�BLICOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Devuelve una copia del objeto
	 */
	public Object clone() {
		try {
			XfslErrorFunction clone = new XfslErrorFunction(this.code);
			double wclone[] = new double[weight.length];
			System.arraycopy(weight,0,wclone,0,weight.length);
			clone.setWeights(wclone);
			return clone;
		} catch(Exception ex) { return null; }
	}

	/**
	 * Obtiene el c�digo de la funci�n
	 */
	public int getCode() { 
		return code; 
	}

	/**
	 * Obtiene el nombre de la funci�n (para la ventana de xfsl)
	 */
	public String getName() {
		switch(code) {
			case MEAN_SQUARE_ERROR: return "Mean Square Error";
			case W_MEAN_SQUARE_ERROR: return "Weighted Mean Square Error";
			case MEAN_ABS_ERROR: return "Mean Absolute Error";
			case W_MEAN_ABS_ERROR: return "Weighted Mean Absolute Error";
			case CLASIF_ERROR: return "Classification Error";
			case ADV_CLASIF_ERROR: return "Advanced Classification Error";
			case NEFCLASS: return "Classification Square Error";
		}
		return null;
	}

	/**
	 * Analiza si es una funci�n de error de clasificaci�n
	 */
	public boolean isClassification() {
		switch(code) {
			case MEAN_SQUARE_ERROR:
			case W_MEAN_SQUARE_ERROR:
			case MEAN_ABS_ERROR:
			case W_MEAN_ABS_ERROR: return false;
			case CLASIF_ERROR:
			case ADV_CLASIF_ERROR:
			case NEFCLASS: return true;
			default: return false;
		}
	}

	/**
	 * Analiza si la funci�n utiliza pesos
	 */
	public boolean isWeighted() {
		return (code == W_MEAN_SQUARE_ERROR || code == W_MEAN_ABS_ERROR);
	}

	/**
	 * Representaci�n en formato XML
	 */
	public String toXML() {
		String eol = System.getProperty("line.separator", "\n");
		String xml = "\t\t<error_function name=\""+codeToName(code)+"\">"+eol;
		for(int i=0; i<weight.length; i++) {
			xml += "\t\t\t<weight value=\""+weight[i]+"\"></weight>"+eol;
		}
		xml += "\t\t</error_function>"+eol;
		return xml;
	}

	/**
	 * Asigna los valores de los pesos
	 */
	public void setWeights(double[] weight) throws XflException {
		if(!isWeighted() && weight.length>0) throw new XflException(25);
		this.weight = weight;
	}

	/**
	 * Obtiene los valores de los pesos
	 */
	public double[] getWeights() {
		return this.weight;
	}

	/**
	 *  Normaliza el valor de los pesos
	 */
	public void normalizeWeights(int numoutputs) {
		if(!isWeighted()) return;
		double aw[] = new double[numoutputs];
		double norm = 0;
		for(int i=0; i<weight.length && i<numoutputs; i++) norm += weight[i];
		for(int i=weight.length; i<numoutputs; i++) norm += 1;
		for(int i=0; i<weight.length && i<numoutputs; i++) aw[i] = weight[i]/norm;
		for(int i=weight.length; i<numoutputs; i++) aw[i] = 1/norm;
		this.weight = aw;
	}

	/**
	 * Evalua la funci�n de error para un conjunto de patrones
	 */
	public XfslEvaluation evaluate(Specification s, XfdsDataSet p, double le) {
		switch(code) {
			case MEAN_SQUARE_ERROR: return MSE(s,p,le);
			case W_MEAN_SQUARE_ERROR: return WMSE(s,p,le);
			case MEAN_ABS_ERROR: return MAE(s,p,le);
			case W_MEAN_ABS_ERROR: return WMAE(s,p,le);
			case CLASIF_ERROR: return CE(s,p,le);
			case ADV_CLASIF_ERROR: return ACE(s,p,le);
			case NEFCLASS: return NCE(s,p,le);
		}
		return null;
	}

	/**
	 * Calcula las derivadas del sistema 
	 */
	public XfslEvaluation compute_derivatives(Specification s, XfdsDataSet p)
	throws XflException{
		switch(code) {
			case MEAN_SQUARE_ERROR: return dMSE(s,p);
			case W_MEAN_SQUARE_ERROR: return dWMSE(s,p);
			case MEAN_ABS_ERROR: return dMAE(s,p);
			case W_MEAN_ABS_ERROR: return dWMAE(s,p);
			case CLASIF_ERROR: return dCE(s,p);
			case ADV_CLASIF_ERROR: return dACE(s,p);
			case NEFCLASS: return dNCE(s,p);
		}
		throw new XflException(19);
	}

	/**
	 * Estima las derivadas del sistema mediante la tangente
	 */
	public XfslEvaluation estimate_derivatives(Specification spec,
			XfdsDataSet dataset, double perturb) {

		Parameter[] adjustable = spec.getAdjustable();
		XfslEvaluation ev1 = evaluate(spec,dataset,1.0);
		for(int i=0; i<adjustable.length; i++) {
			double prev = adjustable[i].value;
			double sign = (Math.random() <0.5? 1.0 : -1.0);
			adjustable[i].setDesp(sign*perturb);
			spec.update();
			XfslEvaluation ev2 = evaluate(spec,dataset,1.0);
			if((adjustable[i].value - prev)/(sign*perturb) >0.001 ) {
				double deriv = (ev2.error - ev1.error)/(adjustable[i].value - prev);
				adjustable[i].addDeriv( deriv );
			}
			adjustable[i].value = prev;
		}
		return ev1;
	}

	/**
	 * Estima las derivadas del sistema de forma grosera
	 */
	public XfslEvaluation stochastic_derivatives(Specification spec,
			XfdsDataSet dataset, double perturb) {

		Parameter[] adjustable = spec.getAdjustable();
		double prev[] = new double[adjustable.length];
		double val1[] = new double[adjustable.length];
		double sign[] = new double[adjustable.length];
		for(int i=0; i<adjustable.length; i++) {
			prev[i] = adjustable[i].value;
			sign[i] = (Math.random() <0.5? 1.0 : -1.0);
			adjustable[i].setDesp(sign[i]*perturb);
		}
		spec.update();
		XfslEvaluation ev1 = evaluate(spec,dataset,1.0);

		for(int i=0; i<adjustable.length; i++) {
			val1[i] = adjustable[i].value;
			adjustable[i].value = prev[i];
			adjustable[i].setDesp(-sign[i]*perturb);
		}
		spec.update();
		XfslEvaluation ev2 = evaluate(spec,dataset,1.0);

		for(int i=0; i<adjustable.length; i++) {
			double val2 = adjustable[i].value;
			adjustable[i].value = prev[i];
			if( (val1[i]-val2)/(sign[i]*perturb) >0.001 )
				adjustable[i].addDeriv((ev2.error - ev1.error)/(val2 - val1[i]));
		}
		return evaluate(spec,dataset,1.0);
	}

	//----------------------------------------------------------------------------//
	//                             M�TODOS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Parser de los identificadores de las funciones
	 */
	private static int nameToCode(String name) {
		if(name.equals("mean_square_error")) return MEAN_SQUARE_ERROR;
		if(name.equals("weighted_mean_square_error")) return W_MEAN_SQUARE_ERROR;
		if(name.equals("mean_absolute_error")) return MEAN_ABS_ERROR;
		if(name.equals("weighted_mean_absolute_error")) return W_MEAN_ABS_ERROR;
		if(name.equals("classification_error")) return CLASIF_ERROR;
		if(name.equals("advanced_classification_error")) return ADV_CLASIF_ERROR;
		if(name.equals("classification_square_error")) return NEFCLASS;
		if(name.equals("MSE")) return MEAN_SQUARE_ERROR;
		if(name.equals("WMSE")) return W_MEAN_SQUARE_ERROR;
		if(name.equals("MAE")) return MEAN_ABS_ERROR;
		if(name.equals("WMAE")) return W_MEAN_ABS_ERROR;
		if(name.equals("CE")) return CLASIF_ERROR;
		if(name.equals("ACE")) return ADV_CLASIF_ERROR;
		if(name.equals("CSE")) return NEFCLASS;
		return -1;
	}

	/**
	 * Identificador asociado a cada funci�n
	 */
	private static String codeToName(int code) {
		switch(code) {
			case MEAN_SQUARE_ERROR: return "MSE";
			case W_MEAN_SQUARE_ERROR: return "WMSE";
			case MEAN_ABS_ERROR: return "MAE";
			case W_MEAN_ABS_ERROR: return "WMAE";
			case CLASIF_ERROR: return "CE";
			case ADV_CLASIF_ERROR: return "ACE";
			case NEFCLASS: return "CSE";
		}
		return null;
	}

	/**
	 * C�lculo de Mean Square Error
	 */
	private XfslEvaluation MSE(Specification spec,XfdsDataSet dataset,double le) {
		SystemModule system = spec.getSystemModule();
		double mxae=0, mse=0, error=0;
		for(int p=0; p<dataset.input.length; p++) {
			double[] output = system.crispInference(dataset.input[p]);
			for(int i=0; i<output.length; i++) {
				double dev = (output[i]-dataset.output[p][i])/dataset.range[i];
				if(dev<0) dev = -dev;
				error += dev*dev/output.length;
				mse += dev*dev/output.length;
				if(dev>mxae) mxae = dev;
			}
		}
		return new XfslEvaluation(error,mse,mxae,le,dataset.input.length);
	}

	/**
	 * C�lculo de Weighted Mean Square Error
	 */
	private XfslEvaluation WMSE(Specification spec,XfdsDataSet dataset,double le) {
		SystemModule system = spec.getSystemModule();
		double mxae=0, mse=0, error=0;
		for(int p=0; p<dataset.input.length; p++) {
			double[] output = system.crispInference(dataset.input[p]);
			for(int i=0; i<output.length; i++) {
				double dev = (output[i]-dataset.output[p][i])/dataset.range[i];
				if(dev<0) dev = -dev;
				error += weight[i]*dev*dev;
				mse += dev*dev/output.length;
				if(dev>mxae) mxae = dev;
			}
		}
		return new XfslEvaluation(error,mse,mxae,le,dataset.input.length);
	}

	/**
	 * C�lculo de Mean Absolute Error
	 */
	private XfslEvaluation MAE(Specification spec,XfdsDataSet dataset,double le) {
		SystemModule system = spec.getSystemModule();
		double mxae=0, mse=0, error=0;
		for(int p=0; p<dataset.input.length; p++) {
			double[] output = system.crispInference(dataset.input[p]);
			for(int i=0; i<output.length; i++) {
				double dev = (output[i]-dataset.output[p][i])/dataset.range[i];
				if(dev<0) dev = -dev;
				error += dev/output.length;
				mse += dev*dev/output.length;
				if(dev>mxae) mxae = dev;
			}
		}
		return new XfslEvaluation(error,mse,mxae,le,dataset.input.length);
	}

	/**
	 * C�lculo de Weighted Mean Absolute Error
	 */
	private XfslEvaluation WMAE(Specification spec,XfdsDataSet dataset,double le) {
		SystemModule system = spec.getSystemModule();
		double mxae=0, mse=0, error=0;
		for(int p=0; p<dataset.input.length; p++) {
			double[] output = system.crispInference(dataset.input[p]);
			for(int i=0; i<output.length; i++) {
				double dev = (output[i]-dataset.output[p][i])/dataset.range[i];
				if(dev<0) dev = -dev;
				error += weight[i]*dev;
				mse += dev*dev/output.length;
				if(dev>mxae) mxae = dev;
			}
		}
		return new XfslEvaluation(error,mse,mxae,le,dataset.input.length);
	}

	/**
	 * C�lculo de Classification Error
	 */
	private XfslEvaluation CE(Specification spec, XfdsDataSet dataset, double le) {
		SystemModule system = spec.getSystemModule();
		double misscr=0, misscn=0, error=0;
		for(int p=0; p<dataset.input.length; p++) {
			double[] output = system.crispInference(dataset.input[p]);
			double norm = output.length * dataset.input.length;
			double err = 0;
			for(int i=0; i<output.length; i++)
				if(output[i] != dataset.output[p][i]) err++;
			error += err/norm;
			misscn += err;
			misscr += err/norm;
		}
		return new XfslEvaluation(error,misscr,misscn,le);
	}

	/**
	 * C�lculo de Advanced Classification Error
	 */
	private XfslEvaluation ACE(Specification spec,XfdsDataSet dataset,double le) {
		SystemModule system = spec.getSystemModule();
		double misscr=0, misscn=0, error=0;
		for(int p=0; p<dataset.input.length; p++) {
			double[] output = system.crispInference(dataset.input[p]);
			double norm = output.length * dataset.input.length;
			AggregateMemFunc[] fuzzyvalue = system.getFuzzyValues();
			double err1=0, err2=0;
			for(int i=0; i<output.length; i++) {
				if(output[i] != dataset.output[p][i]) {
					double degree1 = fuzzyvalue[i].getActivationDegree(output[i]);
					double degree2 = fuzzyvalue[i].getActivationDegree(dataset.output[p][i]);
					err1 += 1 + ( degree1 - degree2 )/output.length; 
					err2++;
				}
			}
			error += err1/norm;
			misscr += err2/norm;
			misscn += err2;
		}
		return new XfslEvaluation(error,misscr,misscn,le);
	}

	/**
	 * C�lculo de Square Classification Error
	 */
	private XfslEvaluation NCE(Specification spec,XfdsDataSet dataset,double le) {
		SystemModule system = spec.getSystemModule();
		double misscr=0, misscn=0, error=0;
		for(int p=0; p<dataset.input.length; p++) {
			double[] output = system.crispInference(dataset.input[p]);
			double norm = output.length * dataset.input.length;
			AggregateMemFunc[] fuzzyvalue = system.getFuzzyValues();
			Variable[] outvar = system.getOutputs();
			double err1 = 0, err2 = 0;
			for(int i=0; i<output.length; i++) {
				LinguisticLabel[] pmf = outvar[i].getType().getAllMembershipFunctions();
				for(int j=0; j<pmf.length; j++) {
					double degree = fuzzyvalue[i].getActivationDegree(pmf[j].center());
					if(pmf[j].center() == dataset.output[p][i])
						err1 += (1-degree)*(1-degree)/pmf.length;
					else err1 += degree*degree/pmf.length;
				}
				if(output[i] != dataset.output[p][i]) err2++;
			}
			error += err1/norm;
			misscr += err2/norm;
			misscn += err2;
		}
		return new XfslEvaluation(error,misscr,misscn,le);
	}

	/**
	 * Derivada de Mean Square Error
	 */
	private XfslEvaluation dMSE(Specification spec, XfdsDataSet dataset) 
	throws XflException {
		SystemModule system = spec.getSystemModule();
		double mxae=0, mse=0, error=0;
		double[] range = dataset.range;
		int numpatterns = dataset.input.length;
		for(int p=0; p<numpatterns; p++) {
			double[] output = system.crispInference(dataset.input[p]);
			double[] target = dataset.output[p];
			double[] deriv = new double[output.length];
			for(int i=0; i<output.length; i++) {
				double dev = (output[i]-target[i])/range[i];
				deriv[i] = 2*dev/(range[i]*output.length*numpatterns);
				if(dev<0) dev = -dev;
				error += dev*dev/output.length;
				mse += dev*dev/output.length;
				if(dev>mxae) mxae = dev;
			}
			system.derivative(deriv);
		}
		return new XfslEvaluation(error,mse,mxae,numpatterns);
	}

	/**
	 * Derivada de Weighted Mean Square Error
	 */
	private XfslEvaluation dWMSE(Specification spec, XfdsDataSet dataset)
	throws XflException {
		SystemModule system = spec.getSystemModule();
		double[] range = dataset.range;
		double mxae=0, mse=0, error=0;
		int numpatterns = dataset.input.length;
		for(int p=0; p<dataset.input.length; p++) {
			double[] output = system.crispInference(dataset.input[p]);
			double[] target = dataset.output[p];
			double[] deriv = new double[output.length];
			for(int i=0; i<output.length; i++) {
				double dev = (output[i]-target[i])/range[i];
				deriv[i] = 2*weight[i]*dev/(range[i]*numpatterns);
				if(dev<0) dev = -dev;
				error += weight[i]*dev*dev;
				mse += dev*dev/output.length;
				if(dev>mxae) mxae = dev;
			}
			system.derivative(deriv);
		}
		return new XfslEvaluation(error,mse,mxae,numpatterns);
	}

	/**
	 * Derivada de Mean Absolute Error
	 */
	private XfslEvaluation dMAE(Specification spec, XfdsDataSet dataset)
	throws XflException {
		SystemModule system = spec.getSystemModule();
		double[] range = dataset.range;
		double mxae=0, mse=0, error=0;
		int numpatterns = dataset.input.length;
		for(int p=0; p<dataset.input.length; p++) {
			double[] output = system.crispInference(dataset.input[p]);
			double[] target = dataset.output[p];
			double[] deriv = new double[output.length];
			for(int i=0; i<output.length; i++) {
				double dev = (output[i]-target[i])/range[i];
				if(dev>0) deriv[i] = 1.0;
				else if(dev<0) deriv[i] = -1.0;
				else deriv[i] = 0;
				deriv[i] /= (range[i]*output.length*numpatterns);
				if(dev<0) dev = -dev;
				error += dev/output.length;
				mse += dev*dev/output.length;
				if(dev>mxae) mxae = dev;
			}
			system.derivative(deriv);
		}
		return new XfslEvaluation(error,mse,mxae,numpatterns);
	}

	/**
	 * Derivada de Weighted Mean Absolute Error
	 */
	private XfslEvaluation dWMAE(Specification spec, XfdsDataSet dataset)
	throws XflException {
		SystemModule system = spec.getSystemModule();
		double[] range = dataset.range;
		double mxae=0, mse=0, error=0;
		int numpatterns = dataset.input.length;
		for(int p=0; p<dataset.input.length; p++) {
			double[] output = system.crispInference(dataset.input[p]);
			double[] target = dataset.output[p];
			double[] deriv = new double[output.length];
			for(int i=0; i<output.length; i++) {
				double dev = (output[i]-target[i])/range[i];
				if(dev>0) deriv[i] = weight[i];
				else if(dev<0) deriv[i] = -weight[i];
				else deriv[i] = 0;
				deriv[i] /= (range[i]*numpatterns);
				if(dev<0) dev = -dev;
				error += weight[i]*dev;
				mse += dev*dev/output.length;
				if(dev>mxae) mxae = dev;
			}
			system.derivative(deriv);
		}
		return new XfslEvaluation(error,mse,mxae,numpatterns);
	}

	/**
	 * Derivada de Classification Error
	 */
	private XfslEvaluation dCE(Specification spec, XfdsDataSet dataset)
	throws XflException {
		throw new XflException(19);
	}

	/**
	 * Derivada de Advanced Classification Error
	 */
	private XfslEvaluation dACE(Specification spec, XfdsDataSet dataset)
	throws XflException {
		throw new XflException(19);
	}

	/**
	 * Derivada de Square Classification Error
	 */
	private XfslEvaluation dNCE(Specification spec, XfdsDataSet dataset)
	throws XflException {
		SystemModule system = spec.getSystemModule();
		double misscr=0, misscn=0, error=0;
		for(int p=0; p<dataset.input.length; p++) {
			double[] output = system.crispInference(dataset.input[p]);
			double normalize = output.length * dataset.input.length;
			AggregateMemFunc[] fuzzyvalue = system.getFuzzyValues();
			Variable[] outvar = system.getOutputs();
			double err1 = 0, err2 = 0;
			for(int i=0; i<output.length; i++) {
				LinguisticLabel[] pmf = outvar[i].getType().getAllMembershipFunctions();
				for(int j=0; j<fuzzyvalue[i].conc.length; j++) {
					double center = fuzzyvalue[i].conc[j].center();
					double degree = fuzzyvalue[i].getActivationDegree(center);
					double norm = dataset.input.length*output.length*pmf.length;
					if(fuzzyvalue[i].conc[j].degree() == degree) 
						if(center == dataset.output[p][i])
							fuzzyvalue[i].conc[j].setDegreeDeriv(-2*(1-degree)/norm);
						else fuzzyvalue[i].conc[j].setDegreeDeriv(2*degree/norm);
				}
				for(int j=0; j<pmf.length; j++) {
					double degree = fuzzyvalue[i].getActivationDegree(pmf[j].center());
					if(pmf[j].center() == dataset.output[p][i])
						err1 += (1-degree)*(1-degree)/pmf.length;
					else err1 += degree*degree/pmf.length; 
				}
				if(output[i] != dataset.output[p][i]) err2++;
			}
			error += err1/normalize;
			misscr += err2/normalize;
			misscn += err2;
			system.derivative();
		}
		return new XfslEvaluation(error,misscr,misscn);
	}
}
